package com.fox.lock.task;

import com.fox.lock.DistributedLockService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;


/**
 * @author: [wujie]
 * @createTime: [2019/6/22 16:12]
 * @description: 基于redis的分布锁确保任务在一台机器上运行
 */
public abstract class AbstractSingleTimerTask extends Thread {
    private static final Logger logger = LoggerFactory.getLogger(AbstractSingleTimerTask.class);
    private volatile boolean singleTimerTaskAlive;
    private volatile DistributedLockService distributedLockService;
    private volatile long periodMills;
    private volatile long periodExpireMills;
    private volatile long lockMills;
    private static final String prefix = "single-task-lock-";
    private String taskName;
    private volatile String lockName;
    private volatile String requestId;
    private ScheduledExecutorService executorService;
    private ScheduledFuture singleTimerTaskScheduledFuture;
    private volatile ScheduledFuture expireScheduledFuture;
    private volatile boolean looper = true;

    /**
     * @param distributedLockService 分布式锁
     * @param taskName               任务名(锁key)
     * @param periodMills            任务执行频率
     * @param lockMills              锁总时间（总是大于任务执行频率)
     * @param periodExpireMills      续时频率
     */
    public AbstractSingleTimerTask(DistributedLockService distributedLockService, String taskName,
                                   long periodMills, long lockMills, long periodExpireMills) {
        this.distributedLockService = distributedLockService;
        this.taskName = taskName;
        this.requestId = UUID.randomUUID().toString().replace("-","");
        this.lockName = prefix + taskName;
        this.periodMills = periodMills;
        this.lockMills = Math.max(periodMills, lockMills);
        executorService = Executors.newScheduledThreadPool(2);
        this.periodExpireMills = periodExpireMills;
    }

    /**
     *
     * @param looper ture 会按照频率执行 flase只执行一次
     */
    public void setLooper(boolean looper){
        this.looper=looper;
    }

    /**
     * @return 任务线程是否存活
     */
    public boolean isSingleTimerTaskAlive() {
        return singleTimerTaskAlive;
    }

    /**
     * 停止任务线程
     */
    public synchronized void stopSingleTimerTask() {
        singleTimerTaskAlive = false;
        if (singleTimerTaskScheduledFuture != null) {
            singleTimerTaskScheduledFuture.cancel(true);
        }
    }

    /**
     * 停止续时线程
     */
    public synchronized void stopExpireThread() {
        if (expireScheduledFuture != null) {
            expireScheduledFuture.cancel(false);
        }
    }

    //业务
    public abstract void executeJob();

    @Override
    public void run() {
        AbstractSingleTimerTask abstractSingleTimerTask = this;
        singleTimerTaskScheduledFuture = executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                //没有争取到锁就放弃 等下个周期(periodMills)再争取
                if (!distributedLockService.tryAquire(lockName, requestId, lockMills)) {
                    logger.debug("[{}]-[{}]没争取到放弃锁", periodExpireMills, requestId);
                    return;
                }
                logger.debug("[{}]-[{}]争取到锁", lockName, requestId);
                //要执行业务代码之前 先把存活状态置为true 以便续时线程可以看到
                singleTimerTaskAlive = true;
                //创建续时Runable
                ExpireThread expireThread = new ExpireThread();
                //初始化
                expireThread.init(abstractSingleTimerTask, distributedLockService,
                        lockName, requestId, lockMills - periodExpireMills, periodExpireMills);
                //执行续时操作
                expireScheduledFuture = executorService.scheduleAtFixedRate(expireThread, 0, periodExpireMills, TimeUnit.MILLISECONDS);
                logger.debug("[{}]-[{}]执行业务代码", lockName, requestId);
                //执行业务逻辑
                try {
                    executeJob();
                } finally {
                    //执行完毕线程
                    singleTimerTaskAlive = false;
                    if (!looper) {
                        stopSingleTimerTask();
                        stopExpireThread();
                    }
                }
            }
        }, 0, periodMills, TimeUnit.MILLISECONDS);
    }
}
